package grooverse.emf;

import groovy.lang.Closure;

import java.util.Map;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;

import grooverse.util.DeferredReferenceHandler;
import grooverse.util.QualifiedBuilderSupport;

public class EMFBuilder extends QualifiedBuilderSupport {

    private String name;
    private DeferredReferenceHandler deferred;

    @SuppressWarnings("unchecked")
    public Object createNodeQualified(Object qualifier, Object name, Map attributes, Object value) {
        this.name = (String) name;
        EClassifier type;
        
        if (qualifier != null) {
            type = (EClassifier) qualifier;
        } else if (getCurrent() != null) {
            EStructuralFeature feature = ((EObject)getCurrent()).eClass().getEStructuralFeature((String)name);
            type = feature.getEType();
        } else {
            type = EcorePackage.eINSTANCE.getEPackage();
        }

        Object result;
        EFactory factory = type.getEPackage().getEFactoryInstance();

        if (type instanceof EClass) {
            EClass eClass = (EClass)type;
            EObject instance = factory.create(eClass);

            if (attributes != null) {
                Map<String, Object> attributeMap = attributes;
                for (Map.Entry<String, Object> entry : attributeMap.entrySet()) {
                    String featureName = entry.getKey();
                    Object featureValue = entry.getValue();
                    if (featureValue instanceof Closure) {
                        getDeferredReferenceHandler().add(instance, featureName, (Closure)featureValue);
                    } else {
                        EStructuralFeature feature = eClass.getEStructuralFeature(featureName);
                        instance.eSet(feature, featureValue);
                    }
                }
            }

            result = instance;
        } else if (type instanceof EDataType){
            result = factory.createFromString((EDataType)type, value == null ? null: value.toString());
        } else {
            throw new IllegalStateException("Type is neither EClass nor EDataType: " + type); //$NON-NLS-1$
        }

        return result;
    }

    public void setParent(Object parent, Object child) {
        if (parent != null) {
            EObject parentEObject = (EObject) parent;
            EStructuralFeature feature = parentEObject.eClass().getEStructuralFeature(this.name);
            
            if (feature.getUpperBound() == 1) {
                parentEObject.eSet(feature, child);
            } else {
                @SuppressWarnings("unchecked")
                EList<Object> list = (EList<Object>)parentEObject.eGet(feature);
                list.add(child);
            }
        }
    }

    public void nodeCompleted(Object parent, Object node) {
        if (parent == null) {
            if (this.deferred != null) {
                this.deferred.resolve();
            }
        }
    }

    private DeferredReferenceHandler getDeferredReferenceHandler() {
        if (this.deferred == null) {
            this.deferred = new DeferredReferenceHandler();
        }

        return this.deferred;
    }

}
